use cranelift::prelude::*;

use rustphp_jit::{RustPhpJITBackend, RustPhpJITBuilder};

use cranelift_module::{Linkage, Module};

fn echo(arg: i64) -> i64 {
    print!("{}", arg);

    0_i64
}

pub struct RustPhpJitInterface {
    builder_context: FunctionBuilderContext,

    ctx: cranelift_codegen::Context,

    module: Module<RustPhpJITBackend>,
}

impl RustPhpJitInterface {
    pub fn new() -> Self {
        let mut builder = RustPhpJITBuilder::new(cranelift_module::default_libcall_names());
        builder.symbol("echo", echo as *const u8);

        let module = Module::new(builder);
        Self {
            builder_context: FunctionBuilderContext::new(),
            ctx: module.make_context(),
            module,
        }
    }

    /// JIT编译过程
    pub fn compile(&mut self, ast: rustphp_parser::ast::Expression) -> Result<*const u8, String> {
        self.translate(ast)?;

        let name = "RustPHP_Main";
        let id = self
            .module
            .declare_function(&name, Linkage::Export, &self.ctx.func.signature)
            .map_err(|e| e.to_string())?;

        self.module
            .define_function(id, &mut self.ctx)
            .map_err(|e| e.to_string())?;

        self.module.clear_context(&mut self.ctx);

        self.module.finalize_definitions();

        let code = self.module.get_finalized_function(id);

        Ok(code)
    }

    /// 把AST转换成IR
    fn translate(&mut self, ast: rustphp_parser::ast::Expression) -> Result<(), String> {
        let int = self.module.target_config().pointer_type();

        let mut builder = FunctionBuilder::new(&mut self.ctx.func, &mut self.builder_context);
        let entry_ebb = builder.create_ebb();
        builder.append_ebb_params_for_function_params(entry_ebb);
        builder.switch_to_block(entry_ebb);
        builder.seal_block(entry_ebb);

        let mut trans = FunctionTranslator {
            int,
            builder,
            module: &mut self.module,
        };

        trans.translate_expr(ast);

        trans.builder.ins().return_(&[]);
        trans.builder.finalize();

        Ok(())
    }
}

struct FunctionTranslator<'a> {
    int: types::Type,
    builder: FunctionBuilder<'a>,
    module: &'a mut Module<RustPhpJITBackend>,
}

impl<'a> FunctionTranslator<'a> {
    fn translate_expr(&mut self, expr: rustphp_parser::ast::Expression) -> Value {
        use rustphp_parser::ast::Expression::*;
        match expr {
            Echo(v) => {
                let args = vec![v];

                self.translate_call("echo".to_string(), args)
            }
        }
    }

    fn translate_call(&mut self, name: String, args: Vec<i64>) -> Value {
        let mut sig = self.module.make_signature();

        for _arg in &args {
            sig.params.push(AbiParam::new(self.int));
        }

        sig.returns.push(AbiParam::new(self.int));

        let callee = self
            .module
            .declare_function(&name, Linkage::Import, &sig)
            .expect("problem declaring function");
        let local_callee = self
            .module
            .declare_func_in_func(callee, &mut self.builder.func);

        let mut arg_values = Vec::new();
        for arg in args {
            arg_values.push(self.builder.ins().iconst(self.int, arg));
        }
        let call = self.builder.ins().call(local_callee, &arg_values);

        self.builder.inst_results(call)[0]
    }
}
